﻿using FlowSequencer.Core.FlowElements;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;

namespace FlowSequencer.Core.Sequencer
{
    public class JohnsonSequencer : Sequencer
    {
        int maxCycles;


        public List<int> OptimalSequencePart1 { get; private set; }
        public List<int> OptimalSequencePart2 { get; private set; }
        private Flow optimizedFlow;

        public JohnsonSequencer(int maxCycles)  // max cycles removed
        {
            this.maxCycles = maxCycles;
        }



        public override void ComputeFlow()
        {
            var optimalSequence = ComputeOptimalSequence();
            optimizedFlow = PrepareFlow(optimalSequence);
        }
        public override Flow GetOptimizedFlow()
        {
            return optimizedFlow;
        }

        private List<int> ComputeOptimalSequence()
        {
            List<Task> line1 = new List<Task>();
            List<Task> line2 = new List<Task>();
            ComputeLinesForAlgorithm(ref line1, ref line2);
            return GetOptiamSequence(line1, line2);
        }

        private Flow PrepareFlow(List<int> optimalSequence)
        {
            Flow optimalizedFlow = ConvertToFlowObject(optimalSequence);
            AddDownTime(optimalizedFlow, optimalSequence);
            return optimalizedFlow;
        }

        private Flow ConvertToFlowObject(List<int> optimalSequence)
        {
            Flow optimalizedFlow = new Flow();
            optimalizedFlow.OptimalSequence = optimalSequence;

            for (int lineIndex = 0; lineIndex < inputFlow.Lines.Count; lineIndex++)
            {
                optimalizedFlow.Lines.Add(new Line());
                foreach (var optimalIndex in optimalSequence)
                {
                    var bestTasks = inputFlow.Lines[lineIndex].Tasks[optimalIndex];
                    optimalizedFlow.Lines[lineIndex].Tasks.Add(bestTasks);
                }
            }
            return optimalizedFlow;
        }
        
        private void AddDownTime(Flow optimalizedFlow, List<int> optimalSequence)
        {
            
            int taskStartCycle = 0;
            int currentIndex = 0;
            int taskStartCycleFromPreviousLine = 0;
            for (int lineIndex = 1; lineIndex < inputFlow.Lines.Count; lineIndex++)
            {
                currentIndex = 0;
                taskStartCycle = 0;
                foreach (var optimalIndex in optimalSequence)
                {
                    var endCycleForTaskOnPreviousLine = ComputeEndCycleForTask(optimalizedFlow.Lines[lineIndex - 1], optimalIndex);
                    var endCycleForCurrentTask = ComputeEndCycleForTask(optimalizedFlow.Lines[lineIndex], optimalIndex);
                    var currentTask = inputFlow.Lines[lineIndex].Tasks[optimalIndex];
                    var startCycleForCurrentTask = endCycleForCurrentTask - currentTask.Cycles;
                    if (endCycleForTaskOnPreviousLine > startCycleForCurrentTask) // current task starts befor end of previous 
                    {
                        var downtime = endCycleForTaskOnPreviousLine - startCycleForCurrentTask; 
                        taskStartCycle += downtime + currentTask.Cycles;
                        optimalizedFlow.Lines[lineIndex].Tasks.Insert(currentIndex, new Task { Cycles = downtime, Name = "" });
                        currentIndex++;
                    }
                    else
                        taskStartCycle += inputFlow.Lines[lineIndex].Tasks[optimalIndex].Cycles;
                    currentIndex++;
                }
            }
        }

        private int ComputeEndCycleForTask(Line line, int taskIndex)
        {
            int sumCycles = 0;
            foreach (var task in line.Tasks)
            {
                sumCycles += task.Cycles;
                if (task.Index == taskIndex)
                    break;
                
            }
            return sumCycles;
        }

        private void ComputeLinesForAlgorithm(ref List<Task> line1, ref List<Task> line2)
        {
            if (inputFlow.Lines.Count == 2)
            {
                line1 = inputFlow.Lines[0].Tasks;
                line2 = inputFlow.Lines[1].Tasks;
            }
            else if (inputFlow.Lines.Count == 3)
            {
                line1 = inputFlow.Lines[0].Tasks.Select(x => x.Clone() as Task).ToList<Task>();


                line1.ForEach( x => { x.Cycles += inputFlow.Lines[1].Tasks.FirstOrDefault(t => t.Name == x.Name).Cycles; });
                line2 = inputFlow.Lines[1].Tasks.Select(x => x.Clone() as Task).ToList<Task>();
                line2.ForEach( x => { x.Cycles += inputFlow.Lines[2].Tasks.FirstOrDefault(t => t.Name == x.Name).Cycles; });
            }
        }

        private List<int> GetOptiamSequence(List<Task> line1, List<Task> line2)
        {
            var part1 = (from process1 in line1
                         join process2 in line2
                         on process1.Name equals process2.Name
                         where process1.Cycles <= process2.Cycles
                         orderby process1.Cycles
                         select process1.Index);

            var part2 = (from process1 in line1
                         join process2 in line2
                         on process1.Name equals process2.Name
                         where process1.Cycles > process2.Cycles
                         orderby process1.Cycles descending
                         select process1.Index);

            OptimalSequencePart1 = part1.ToList<int>();
            OptimalSequencePart2 = part2.ToList<int>();
            return part1.Union(part2).ToList<int>();
        }

        private bool HasTooManyCycles(Line line)
        {
            if (line.Cycles > maxCycles) return false;
            return true;
        }



 
    }

}
